Rework class syntax to make the code base legible for language servers#9861
Rework class syntax to make the code base legible for language servers#9861vaisest wants to merge 38 commits into
Conversation
|
There are some annotation examples in If you're getting Claude (or w/e) to help, make sure it's only considering the latest (rust) version of EmmyLua, not the old .NET or Java ones. |
|
The latest PR changes the way classes are constructed. Instead of the constructor being in the varargs of newClass, it's now a separately defined method, which is called by new(). Basically, class creation is now: ---@class Example
local ExampleClass = newClass("Example", "Parent1", "Parent2")
---@param whatever: WhatTypeItIs it's a good idea to type these
function ExampleClass:Example(whatever)
self.whatever = whatever
endWhich means the class type has information that self.whatever exists. In most cases this isn't super useful as everything is any, but at least now typing these isn't completely useless. It also works well for discovering fields and methods, since now you can use the IDE "go to definition" button to go to, e.g. a method added by the parent class. It's also possible to add more fields to the class with At least with Sumneko's LSP, there were also warnings about making a class with a typo in the class name. This linter PR also seems relevant, as these changes make linters much more useful: https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/1796/changes Using parent class constructors still looks a bit weird. For example
|
|
The last commit makes the proxy metatable (or whatever it's called) not supply self to the parent constructor. This means that you now use : to call it and type checking works correctly, as the LSP sees the self variable. |
|
A local Codex-assisted analysis harness, followed by a Claude review of the findings, was used to sanity-check the current PR head ( Checks covered The pass found two runtime issues.
The Export smoke fails with: The minimal fix that passed locally was to convert those classes to the named constructor style already used by the migrated local XClass = newClass("X", "Parent")
function XClass:X(...)
...
end
If function SetCallback(name, func)
__callbackTable__[name] = func
end
function GetCallback(name)
return __callbackTable__[name]
endIn the current smoke this only matters if anything calls After applying those two local changes, these checks passed: LuaLS also reports new diagnostics on existing dynamic patterns that the annotations currently model too narrowly, such as function-valued control Manual UI testing was not done. |
|
Commit c0a223e changes new() to not call the constructor. This is motivated by the lack of type checking on varargs. It wraps the constructor to check parent initialisation. The construction syntax would now be different: new("Control"):Control(nil, {0, 4, 0, 0})The codebase was updated using a regex to do this. This is useful, because the LSP can now see what you should be putting into the constructor, instead of only seeing any-typed varargs:
But it does change the code style more than the previous changesm as those were strictly limited to class definition. Thoughts? |
|
One old-style constructor call seems to remain after the latest constructor change.
toast.dismissButton = new(
"ButtonControl",
...
)Since A scan of The minimal conversion should be: toast.dismissButton = new("ButtonControl"):ButtonControl(
{ "BOTTOMLEFT", anchorMain, "BOTTOMLEFT" },
{ 4, 0, 80, 20 },
"Dismiss",
function()
dismissedIds[toast.id] = true
toast.mode = "HIDING"
toast.start = GetTime()
end
) |
|
So far the biggest remaining problem is the use of |
|
You can configure "runtime": {
"version": "LuaJIT",
"requireLikeFunction": ["LoadModule"]
},and write ---@generic T
---@param classname `T`
---@return T
function new(classname)
endget better lint and completion |
That's true, but the behaviour of loadmodule is different. Some modules get an array passed to them which they then add variables to, for example. It would probably be better to just remove these cases, as they seem pretty weird. This does make sense for now, though. |
|
Another pattern that I've noticed doesn't get type checked well is: This seems to be quite common in the code base. Replacing these with functions might be required in some places. |
|
Out of curiosity, since you're willing to fix the whitespace issues, why not choose to format the code? I see that the formatting in many places is still quite messy. I'm not really involved with this project itself—just happened to stumble upon it, since you're using a tool I wrote. |
|
Howdy! From what I gather It's "on the list" of things to do, but it's super low-priority. Also there is the concern that large format changes could potentially obfuscate bugs and make running things like git-blame trickier. |
Commit ffed657 fixes this. Modules were converted to not use the non-standard behaviour of
|
…`) to fix type checking
…f using new() and varargs
|
@Wires77 Would you be able to take a look at this? It seems like it'd be best to get this out of the way before there's more PR activity when patch notes and the league are out |

The way
newClass,new(), and the class constructors work today makes the codebase impossible for a Lua language server to understand. The constructor uses varargs andnew("Foo")returns anany. So the LS has no idea whatnew("ButtonControl")gives you, what fields it has, or what arguments its constructor takes. This effectively means that many IDE features such as being able to go to definition/references, and autocomplete do not work in most cases. The code running in a custom runtime that has its own API also causes trouble as none of it is actually defined in the codebase.This PR attempts to fix those issues. I think this would make it quite a bit easier to contribute to the project, and it could be expanded on to, for example add type complete type definitions for the calculation-related parts of the program (mods, moddbs, etc). Much more information is already available in the IDE now. The runtime API is hinted by adding a definition file. There was no proper documentation for this previously, and I had to check the SimpleGraphic repo for a lot of it.
This PR would also help with the possibility of adding a linting action. As far as I can see, currently it is not really feasible to have linting in this project as there are way too many false errors. I've noticed there have been a few code paths where code accidentally accesses an undefined global variable and which would crash. These could be avoided.
This PR makes very little logic changes. The new() adjustments were made with a regex, and can be verified easily, but I did the constructor changes by hand. Most of the other minor corrections are to code that is already obviously incorrect. This PR changes most files, but the changes can be summed up to:
newClass()to not take in a constructor.---@class ClassName: ParentNameto declare it.:operator instead of.to call parent constructors to help type check them.new()no longer returns the object so that the constructor can be type checkednew("Example"):Example()require(), which meansLoadModule()can be treated by the language server as if it wasrequire().src/_SimpleGraphic.def.lua.gitignorewas adjusted similarly.Remaining issues:
self:GetProperty("var"). This needs manual type hinting (---@field var (fun(): T) | T), unless there is some solution I'm not aware of.requiredoesn't support..which means the imports don't resolve. These should probably be manually hinted using---@module. EmmyLua also has support for amoduleMapoption.---@field.PLoadModulewas not changed